﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading;
using System.Linq.Expressions;
using Castle.DynamicProxy;
using Elf.ServiceModel.Interfaces;

namespace Elf.ServiceModel
{
    /// <summary>
    /// The WCF client invoker.
    /// </summary>
    public class WcfClientInvoker : IWcfClientInvoker
    {
        /// <summary>
        /// Invokes the specified invocation.
        /// </summary>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="invocation">The invocation.</param>
        /// <param name="channel">The obj.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public virtual object Invoke(IInvocation invocation, ICommunicationObject channel, int numberOfRetries, int retryTimeout, object[] parameters)
        {
            if (invocation == null) throw new ArgumentNullException("invocation");
            if (channel == null) throw new ArgumentNullException("channel");
            if (parameters == null) throw new ArgumentNullException("parameters");

            do
            {
                try
                {
                    return invocation.Method.Invoke(channel, parameters);
                }
                catch (TimeoutException)
                {
                    if (!HandleException(channel, numberOfRetries, retryTimeout, abortClient: true)) throw;
                }
                catch (EndpointNotFoundException)
                {
                    if (!HandleException(channel, numberOfRetries, retryTimeout, abortClient: false)) throw;
                }
                catch (CommunicationException)
                {
                    if (!HandleException(channel, numberOfRetries, retryTimeout, abortClient: true)) throw;
                }

                --numberOfRetries;

            } while (numberOfRetries > 0);

            throw new InvalidProgramException("Exception should have been thrown or result should have been returned.");
        }

        /// <summary>
        /// Handles the exception.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="reinitializeMethod">The reinitialize method.</param>
        /// <param name="numberOfRetries">The number of retries.</param>
        /// <param name="retryTimeout">The retry timeout.</param>
        /// <param name="abortClient">if set to <c>true</c> [abort client].</param>
        /// <returns></returns>
        private static bool HandleException(ICommunicationObject client, int numberOfRetries, int retryTimeout, bool abortClient)
        {
            // Only abort if needed.
            if (abortClient) client.Abort();

            // Had enough?
            if (numberOfRetries >= 4) return false;

            // Sleep before reinitializing
            Thread.Sleep(retryTimeout);

            return true;
        }

    }
}
